import ws from 'ws';
import winston from 'winston';
import url from 'url';
import { isObject } from 'lodash';

import { Dispatcher } from '../common/dispatcher.js';

const commandPattern = /^([a-zA-Z_]+)\s{0,}(.*)$/;

/**
 * WebSocket Server
 * @type {WebSocketServer}
 */
let wss;

/**
 * 新的连接
 * @type {String}
 */
export const __NEW_CONNECTION__ = '__NEW_CONNECTION__';

/**
 * WebSocket Channel
 * @type {String}
 */
export const WEBSOCKET_CHANNEL = Symbol('WEBSOCKET_CHANNEL');

/**
 * WebSocket Channel Dispatcher
 * @type {String}
 */
class WebSocketChannelDispatcher extends Dispatcher {
    constructor() {
        super(WEBSOCKET_CHANNEL);
    }

    /**
     * Dispatch a action to all listeners
     * @param  {string}             options.type
     * @param  {Object}             options.payload
     * @param  {string|socket|bool} options.to
     * @return {Dispatcher}
     */
    dispatch({ type, payload = {}, meta, to = false }) {
        super.dispatch({ type, payload });

        // Broadcasting
        if (to === 'everyone' && wss) {
            // Broadcast to all connected sockets
            return wss.clients.forEach(client => {
                client.send(JSON.stringify({
                    type,
                    payload
                }));
            });
        }

        // Send to specific client
        if (isObject(to) && 'send' in to) {
            to.send(JSON.stringify({
                type,
                payload
            }));
        }
    }

    /**
     * Register a callback that will be executed
     * after an new connection was established
     * @param  {Function} handler
     */
    onNewConnection(handler) {
        this.on(__NEW_CONNECTION__, function ({ socket } = {}) {
            return handler(socket);
        });
    }
}

/**
 * WebSocket Channel Dispatcher Instance
 * @type {String}
 */
export const websocketDispatcher = new WebSocketChannelDispatcher();

/**
 * 解析 websocket 指令
 * @param  {string} message
 * @return {Object}
 */
function parseCommand(message) {
    const result = commandPattern.exec(message);

    return result ?
        { cmd: result[1], args: result[2] } : {};
}

/**
 * Handler on new connection
 * @param  {Socket} ws
 */
function onConnection(ws) {
    const location = url.parse(ws.upgradeReq.url, true);

    ws.on('message', message => {
        try {
            const { type, payload } = JSON.parse(message);

            return websocketDispatcher.dispatch({
                type,
                payload,
                meta: {
                    path: location.pathname
                }
            });
        } catch (e) {
            winston.error(`Unable to parse websocket message: ${message}`);
        }
    });

    ws.on('close', () => {
        // Destroy listeners
    });

    // Trigger `__NEW_CONNECTION__` event listeners
    websocketDispatcher.emit(__NEW_CONNECTION__, {
        socket: ws,
        location
    });
}

/**
 * Attach websocket handler to existed web server
 * @param  {Server} server
 */
export function attachWebSocket(server) {
    wss = new ws.Server({ server });

    wss.on('connection', onConnection);
}
